package weeny.pre.io.parser;

import java.util.ArrayList;
import java.util.Collections;
import java.util.TreeMap;
import java.util.List;
import java.util.Map;

import prefuse.data.column.Column;
import prefuse.data.event.ColumnListener;
import prefuse.data.event.EventConstants;
import prefuse.data.parser.DataParseException;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ParserFactory;
import weeny.util.Counter;

/**
 * This one is typically used in PreJTable to make the cells edited like
 * a combo box. How to use this class, please refer to {@link #SHAPE_PARSER}.
 * 
 * Please notice that we do not handle illegal values which are input directly
 * through the table.
 * 
 * @author weeny 
 */
public class EnumParser implements DataParser, ColumnListener{
	/**
	 * [_shape] is a int column, but it only takes a few choices,
	 * so we build it an EnumParser. The illegal values will be handled by
	 * ShapeRenderer, so we do not need to listen to the column changes, and
	 * check if it is legal or not by ourselves.
	 */
	public static final EnumParser SHAPE_PARSER = new EnumParser(int.class);
	static{
		String[] shapeOptions = new String[]{
			"None", "Rectangle", "Ellipse", "Diamond", "Cross", "Star",
			"Triangle up", "Triangle down", "Triangle left", "Triangle right", 
			"Hexagon"};
		for(int i = 0; i<shapeOptions.length; i++){
			SHAPE_PARSER.setKey(new Integer(i-1),shapeOptions[i]);
		}
		SHAPE_PARSER.lock();
	}
	
	protected List<Object> values = new ArrayList<Object>();
	protected List<String> keys = new ArrayList<String>();
	protected Counter<Object> counter = new Counter<Object>();
	protected Class type = null;
	protected boolean isLocked = false;
	protected DataParser traditionalParser = null;
	public List<String> getKeys(){
		return Collections.unmodifiableList(keys);
	}
	public boolean isLocked(){
		return isLocked;
	}
	public void lock(){
		isLocked = true;
	}
	public boolean replaceKey(String oldKey, String newKey){
		if (isLocked) {
			throw new IllegalArgumentException("already locked");
		}
		int idx = keys.indexOf(oldKey);
		if (idx>=0) {
			keys.set(idx, newKey);
			return true;
		}
		return false;
	}
	public boolean setKey(Object value, String key){
		if (isLocked) {
			throw new IllegalArgumentException("already locked");
		}
		int idx = values.indexOf(value);
		if (keys.contains(key)){
			return false;
		}
		else if (idx>=0) {
			keys.set(idx, key);
		}
		else{
			values.add(value);
			keys.add(key);
		}
		return true;
	}
	protected EnumParser(Class type){
		this.type = type;
		traditionalParser = ParserFactory.getDefaultFactory().getParser(type);
	}
	/**
	 * Not a good idea to use this constructor. Never tested.
	 * 
	 * It supposes to add every possible object to the options, which are
	 * displayed as their string values. And it also adds a listener to listen
	 * to the updates in this column and add more new objects to the options.
	 * But it does not remove anything.
	 */
	public EnumParser(Column column) {
		this.type = column.getColumnType();
		Map<String, Object> map = new TreeMap<String, Object>();
		try{
			for(int i = 0; i<Integer.MAX_VALUE; i++){
				Object value = column.get(i);
				if (value == null) {
					map.put("null", value);
				}
				else {
					map.put(value.toString(), value);
				}
			}
		}catch (Exception e) {
			//because we do not know the actual size of column...
			//we just keep trying until it says enough (by throwing out
			//an exception~)
		}
		for(String string : map.keySet()){
			values.add(map.get(string));
			keys.add(string);
		}
		//we add this parser to listen to the changes in the column
		//and record any new data values
		column.addColumnListener(this);
	}
	
	public boolean canParse(String text) {
		try {
			parse(text);
		} catch (DataParseException e) {
			return false;
		}
		return true;
	}

	public boolean compatible(Class type) {
		return false;
	}

	public String format(Object value) {
		return keys.get(values.indexOf(value));
	}

	public Class getType() {
		return type;
	}

	public Object parse(String text) throws DataParseException {
		int index = keys.indexOf(text);
		if (index >= 0) {
			return values.get(index);
		}
		else if (traditionalParser != null)
			return traditionalParser.parse(text);
		throw new DataParseException("Cannot handle "+text);
	}
	
	//we do not care if some values are removed from the column, therefore
	//the key/value pairs are keeping growing
	public void columnChanged(Column src, int type, int start, int end) {
		if(type != EventConstants.UPDATE){
			return;
		}
		for(int i = start; i<=end; i++){
			if(values.indexOf(src.get(i))>=0){
				//the value is already there
				continue;
			}
			if (isLocked()) {
				throw new IllegalArgumentException(
				"The column is locked, but the value is not found");
			}
			else {
				String key = src.get(i).toString();
				if (keys.contains(key)){
					key+="@"+values.size();
				}
				values.add(src.get(i));
				keys.add(key);
			}
		}
	}
	public void columnChanged(Column src, int idx, int prev) {
		columnChanged(src, EventConstants.UPDATE, idx, idx);
	}
	public void columnChanged(Column src, int idx, long prev) {
		columnChanged(src, EventConstants.UPDATE, idx, idx);		
	}
	public void columnChanged(Column src, int idx, float prev) {
		columnChanged(src, EventConstants.UPDATE, idx, idx);		
	}
	public void columnChanged(Column src, int idx, double prev) {
		columnChanged(src, EventConstants.UPDATE, idx, idx);		
	}
	public void columnChanged(Column src, int idx, boolean prev) {
		columnChanged(src, EventConstants.UPDATE, idx, idx);		
	}
	public void columnChanged(Column src, int idx, Object prev) {
		columnChanged(src, EventConstants.UPDATE, idx, idx);		
	}
}